1   /*
2    * Copyright (c) 2009, Oracle and/or its affiliates. All rights reserved.
3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4    *
5    * This code is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU General Public License version 2 only, as
7    * published by the Free Software Foundation.  Oracle designates this
8    * particular file as subject to the "Classpath" exception as provided
9    * by Oracle in the LICENSE file that accompanied this code.
10   *
11   * This code is distributed in the hope that it will be useful, but WITHOUT
12   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14   * version 2 for more details (a copy is included in the LICENSE file that
15   * accompanied this code).
16   *
17   * You should have received a copy of the GNU General Public License version
18   * 2 along with this work; if not, write to the Free Software Foundation,
19   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20   *
21   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22   * or visit www.oracle.com if you need additional information or have any
23   * questions.
24   */
25  
26  /* @test
27   @summary Test rendering when using precise timestamps */
28  
29  import java.util.Arrays;
30  import java.util.Random;
31  
32  import javax.sound.midi.MidiChannel;
33  import javax.sound.midi.Receiver;
34  import javax.sound.midi.ShortMessage;
35  import javax.sound.midi.Soundbank;
36  import javax.sound.sampled.AudioFormat;
37  import javax.sound.sampled.AudioInputStream;
38  
39  import com.sun.media.sound.AudioFloatConverter;
40  import com.sun.media.sound.AudioSynthesizer;
41  import com.sun.media.sound.ModelAbstractChannelMixer;
42  import com.sun.media.sound.ModelChannelMixer;
43  import com.sun.media.sound.SF2Instrument;
44  import com.sun.media.sound.SF2InstrumentRegion;
45  import com.sun.media.sound.SF2Layer;
46  import com.sun.media.sound.SF2LayerRegion;
47  import com.sun.media.sound.SF2Sample;
48  import com.sun.media.sound.SF2Soundbank;
49  import com.sun.media.sound.SimpleInstrument;
50  import com.sun.media.sound.SimpleSoundbank;
51  import com.sun.media.sound.SoftSynthesizer;
52  
53  public class TestPreciseTimestampRendering {
54  
55      public static AudioFormat format = new AudioFormat(44100, 16, 1, true,
56              false);
57  
58      public static SF2Soundbank createTestSoundbank() {
59          // Create impulse instrument
60          // used to measure timing of note-on playback
61          SF2Soundbank soundbank = new SF2Soundbank();
62          float[] data = new float[100];
63          Arrays.fill(data, 0);
64          data[0] = 1.0f;
65          byte[] bdata = new byte[data.length * format.getFrameSize()];
66          AudioFloatConverter.getConverter(format).toByteArray(data, bdata);
67  
68          SF2Sample sample = new SF2Sample(soundbank);
69          sample.setName("Test Sample");
70          sample.setData(bdata);
71          sample.setSampleRate((long) format.getSampleRate());
72          sample.setOriginalPitch(69);
73          soundbank.addResource(sample);
74  
75          SF2Layer layer = new SF2Layer(soundbank);
76          layer.setName("Test Layer");
77          soundbank.addResource(layer);
78          SF2LayerRegion region = new SF2LayerRegion();
79          region.setSample(sample);
80          layer.getRegions().add(region);
81  
82          SF2Instrument ins = new SF2Instrument(soundbank);
83          ins.setName("Test Instrument");
84          soundbank.addInstrument(ins);
85          SF2InstrumentRegion insregion = new SF2InstrumentRegion();
86          insregion.setLayer(layer);
87          ins.getRegions().add(insregion);
88  
89          return soundbank;
90      }
91  
92      public static Soundbank createTestSoundbankWithChannelMixer() {
93          SF2Soundbank soundbank = createTestSoundbank();
94  
95          SimpleSoundbank simplesoundbank = new SimpleSoundbank();
96          SimpleInstrument simpleinstrument = new SimpleInstrument() {
97  
98              public ModelChannelMixer getChannelMixer(MidiChannel channel,
99                      AudioFormat format) {
100                 return new ModelAbstractChannelMixer() {
101                     boolean active = true;
102 
103                     public boolean process(float[][] buffer, int offset, int len) {
104                         for (int i = 0; i < buffer.length; i++) {
105                             float[] cbuffer = buffer[i];
106                             for (int j = 0; j < cbuffer.length; j++) {
107                                 cbuffer[j] = -cbuffer[j];
108                             }
109                         }
110                         return active;
111                     }
112 
113                     public void stop() {
114                         active = false;
115                     }
116                 };
117             }
118 
119         };
120         simpleinstrument.add(soundbank.getInstruments()[0]);
121         simplesoundbank.addInstrument(simpleinstrument);
122 
123         return simplesoundbank;
124     }
125 
126     public static void main(String[] args) throws Exception {
127         test(createTestSoundbank());
128         test(createTestSoundbankWithChannelMixer());
129     }
130 
131     public static void test(Soundbank soundbank) throws Exception {
132 
133         // Create instance of synthesizer using the testing soundbank above
134         AudioSynthesizer synth = new SoftSynthesizer();
135         AudioInputStream stream = synth.openStream(format, null);
136         synth.unloadAllInstruments(synth.getDefaultSoundbank());
137         synth.loadAllInstruments(soundbank);
138         Receiver recv = synth.getReceiver();
139 
140         // Set volume to max and turn reverb off
141         ShortMessage reverb_off = new ShortMessage();
142         reverb_off.setMessage(ShortMessage.CONTROL_CHANGE, 91, 0);
143         recv.send(reverb_off, -1);
144         ShortMessage full_volume = new ShortMessage();
145         full_volume.setMessage(ShortMessage.CONTROL_CHANGE, 7, 127);
146         recv.send(full_volume, -1);
147 
148         Random random = new Random(3485934583945l);
149 
150         // Create random timestamps
151         long[] test_timestamps = new long[30];
152         for (int i = 1; i < test_timestamps.length; i++) {
153             test_timestamps[i] = i * 44100
154                     + (int) (random.nextDouble() * 22050.0);
155         }
156 
157         // Send midi note on message to synthesizer
158         for (int i = 0; i < test_timestamps.length; i++) {
159             ShortMessage midi_on = new ShortMessage();
160             midi_on.setMessage(ShortMessage.NOTE_ON, 69, 127);
161             recv.send(midi_on,
162                     (long) ((test_timestamps[i] / 44100.0) * 1000000.0));
163         }
164 
165         // Measure timing from rendered audio
166         float[] fbuffer = new float[100];
167         byte[] buffer = new byte[fbuffer.length * format.getFrameSize()];
168         long firsts = -1;
169         int counter = 0;
170         long s = 0;
171         long max_jitter = 0;
172         outerloop: for (int k = 0; k < 10000000; k++) {
173             stream.read(buffer);
174             AudioFloatConverter.getConverter(format).toFloatArray(buffer,
175                     fbuffer);
176             for (int i = 0; i < fbuffer.length; i++) {
177                 if (fbuffer[i] != 0) {
178                     if (firsts == -1)
179                         firsts = s;
180 
181                     long measure_time = (s - firsts);
182                     long predicted_time = test_timestamps[counter];
183 
184                     long jitter = Math.abs(measure_time - predicted_time);
185 
186                     if (jitter > 10)
187                         max_jitter = jitter;
188 
189                     counter++;
190                     if (counter == test_timestamps.length)
191                         break outerloop;
192                 }
193                 s++;
194             }
195         }
196         synth.close();
197 
198         if (counter == 0)
199             throw new Exception("Nothing was measured!");
200 
201         if (max_jitter != 0) {
202             throw new Exception("Jitter has occurred! "
203                     + "(max jitter = " + max_jitter + ")");
204         }
205 
206     }
207 
208 }